EP10. 퍼플렉시티와 AI로 모델 평가하기

한 줄 요약: 토큰 확률에서 퍼플렉시티(PPL)를 계산해 언어 모델의 예측력을 정량 평가하고, LLM-as-judge의 환각·평가 모호성 한계를 이해합니다.


0. 사전 필수 용어 (선행지식)

본 주제 이해에 필요한 핵심 용어를 1~2문장 정의와 비유로 정리합니다.

  • 1. 토큰 (Token): LLM이 텍스트를 처리하는 최소 단위로, 단어 또는 단어 조각입니다. 예: "perplexity"는 여러 토큰으로 분리될 수 있습니다. § 3~4에서 PPL 계산의 기본 단위로 사용됩니다.

  • 2. 소프트맥스 (Softmax): 모델 내부의 로짓(logit) 값을 0~1 사이의 확률 분포로 변환하는 함수입니다. 피자 가격표처럼, 각 항목의 상대적 가능성을 합이 1이 되도록 정규화합니다. § 3의 PPL 계산 Step 1에서 토큰 확률 추출에 사용됩니다.

  • 3. 자연로그 (ln, log_e): 밑(base)이 자연상수 e(≈2.718)인 로그입니다. 확률의 곱셈을 덧셈으로 바꾸는 데 사용하며, exp()의 역함수입니다. PPL = exp(H_e) 수식의 핵심입니다.

  • 4. 교차 엔트로피 (Cross-Entropy): 모델이 예측한 확률 분포와 실제 정답 분포 간 차이를 측정하는 지표입니다. 각 토큰의 -ln(P)를 합산한 값으로, 낮을수록 예측이 정확합니다. § 3에서 PPL의 직접 재료로 사용됩니다.

  • 5. 할루시네이션 (Hallucination): LLM이 사실과 다른 내용을 그럴싸하게 생성하는 현상입니다. LLM은 본질적으로 확률적 토큰 생성기이므로, 이 현상은 설계상 피하기 어렵습니다. § 7에서 LLM-as-judge의 핵심 한계로 다룹니다.


1. 주제 정의

퍼플렉시티(Perplexity, PPL)는 언어 모델이 주어진 텍스트 샘플을 얼마나 잘 예측하는지 나타내는 정규화된 평가 지표로, 교차 엔트로피에 지수 함수(exp)를 적용한 값입니다.

핵심 아이디어: 로그 우도의 단위 차이를 제거하고 PPL 하나로 모델 예측 성능을 직관적으로 비교할 수 있습니다.


2. 풀려는 문제

  • 문제 1 — 예측 성능의 정량 비교: 교차 엔트로피는 단위(nats vs bits)에 따라 숫자가 달라져 모델 간 공정 비교가 어렵습니다. PPL은 이 단위 의존성을 제거합니다.

  • 문제 2 — 로그 확률 계산의 수치 불안정성: 연속 토큰의 결합 확률은 극소 소수(예: 0.9 × 0.8 × 0.9 × 0.9 = 0.5832)가 되어 수치 처리가 불안정합니다. 로그 변환으로 곱셈을 덧셈으로 전환합니다.

  • 문제 3 — LLM-as-judge 신뢰성: LLM이 "2.7점"처럼 수치를 직접 생성하면, 이는 계산 결과가 아닌 확률적 말짓기입니다. 분류·판정(True/False) 방식으로 전환해야 합니다.

  • 문제 4 — 벤치마크 튜닝 맹점: 벤치마크 점수가 높다고 실제 사용자 만족도가 높은 것은 아닙니다. 인간 가치 중심 평가가 병행되어야 합니다.

💡 실무 노하우: PPL은 NLP 후처리 보정 없이 모델 내부 로짓에서 직접 계산되므로, 평가 방법 자체에 의한 편향이 없습니다. 이 때문에 연구자들이 PPL을 선호합니다.


3. 핵심 개념·구조

  • 개념 A — 토큰 확률과 소프트맥스: 평가 데이터 "A B C D"를 모델에게 출력하도록 지시합니다. 모델은 각 위치의 로짓을 소프트맥스에 통과시켜 각 토큰의 선택 확률을 구합니다. 예: A=0.9, B=0.8, C=0.9, D=0.9.

  • 개념 B — 로그 우도(LogProb)와 교차 엔트로피: 각 확률에 자연로그를 취하고 음수(-)로 변환합니다. ln(0.9) ≈ -0.105이므로 -ln(0.9) ≈ 0.105. 이 값들을 합산하면 교차 엔트로피 H_e입니다.

  • 자연로그(e 밑) 기준: H_e = 0.13481 (단위: nats)
  • 밑 2 기준: H_2 = 0.19448 (단위: bits), 변환식: H_2 = H_e × log₂(e)

  • 개념 C — PPL = exp(H): 교차 엔트로피에 exp()를 적용해 PPL을 구합니다.

  • PPL_e = exp(0.13481) ≈ 1.1443
  • PPL_2 = 2^0.19448 ≈ 1.1443 (동일한 값)
  • 1에 가까울수록 완벽한 예측, 100에 가까울수록 랜덤 수준입니다.

  • 개념 D — LLM 평가 기준 2축: (1) 학습 적합성 — 교차 엔트로피·PPL로 모델이 해당 도메인 데이터를 얼마나 잘 예측하는가. (2) 기능 정확성 — 정해진 답과의 비교, 임베딩 유사도, 평가용 LLM 판정.

  • 개념 E — LLM-as-judge의 구조적 한계: LLM은 말짓기 시스템이므로 2.6과 2.7을 구분하는 계산 능력이 없습니다. "1~5점 소수점 한 자리로 평가해 줘"라는 요청에 2.7을 생성하는 것은 할루시네이션입니다.

[PPL 계산 흐름]

입력 토큰: A    B    C    D
               |    |    |    |
소프트맥스:  0.9  0.8  0.9  0.9
               |    |    |    |
-ln(P):     0.105 0.223 0.105 0.105
               |
합산 (교차 엔트로피 H_e): 0.13481 nats
               |
exp(H_e) = PPL: 1.1443  ← 1에 가까움 = 좋은 예측

4. 구현 가이드 (Do It Yourself)

시작 전 (Before you begin)

이 섹션을 완료하면 OpenAI API의 logprobs로 PPL을 직접 계산하고, Anthropic Claude API로 LLM-as-judge 분류 평가 코드를 작성할 수 있습니다.

선수 조건: Python 3.10+, API 키(OPENAI_API_KEY, ANTHROPIC_API_KEY)

pip install openai anthropic numpy evaluate

소요 시간: 약 30분.


Step 1 — 토큰 확률 추출 (OpenAI logprobs)

목표: OpenAI API에서 각 토큰의 로그 확률을 추출합니다.

import openai, os, math

client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])

def get_logprobs(text: str, model: str = "gpt-4o-mini") -> list[float]:
    """주어진 텍스트의 토큰별 logprob 리스트를 반환합니다."""
    resp = client.completions.create(
        model=model,
        prompt=text,
        max_tokens=0,
        echo=True,
        logprobs=1,
    )
    token_logprobs = resp.choices[0].logprobs.token_logprobs
    return [lp for lp in token_logprobs if lp is not None]

⚠️ 주의: logprobs=True 옵션은 응답 페이로드 크기를 증가시킵니다. 대량 평가 시 토큰 비용을 모니터링하세요.

확인: token_logprobs 리스트가 텍스트 토큰 수와 일치하는지 확인합니다.


Step 2 — 교차 엔트로피 → PPL 계산

목표: 로그 우도에서 교차 엔트로피와 PPL을 계산합니다.

import numpy as np

def compute_ppl(logprobs: list[float]) -> dict:
    """LogProb 리스트에서 교차 엔트로피(nats)와 PPL을 계산합니다."""
    neg_logprobs = [-lp for lp in logprobs]          # -ln(P)
    cross_entropy_nats = float(np.mean(neg_logprobs)) # H_e (nats)
    ppl = math.exp(cross_entropy_nats)                # PPL = exp(H_e)
    return {"cross_entropy_nats": cross_entropy_nats, "ppl": ppl}

# 사용 예시
lps = get_logprobs("The quick brown fox")
result = compute_ppl(lps)
print(f"PPL: {result['ppl']:.4f}")

💡 실무 노하우: nats와 bits 중 어느 단위를 쓰더라도 exp() 또는 2^() 변환 후 PPL 값은 동일합니다. 하나의 단위로 일관성 있게 사용하세요.

확인: PPL이 1.0~수십 사이 값으로 나오면 정상입니다. 수백 이상이면 도메인 불일치를 의심합니다.


Step 3 — LLM-as-judge 분류 평가 (Anthropic Claude)

목표: 수치 점수 대신 True/False 판정으로 LLM-as-judge를 구현합니다.

import anthropic, os

client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

JUDGE_PROMPT = """다음 질문과 답변을 평가하세요.
질문: {question}
답변: {answer}

판정 기준: 답변이 질문에 사실적으로 정확하게 응답하는가?
반드시 "TRUE" 또는 "FALSE" 중 하나만 출력하세요."""

def judge_answer(question: str, answer: str) -> bool:
    """답변의 사실 정확성을 True/False로 판정합니다."""
    msg = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=10,
        messages=[{
            "role": "user",
            "content": JUDGE_PROMPT.format(question=question, answer=answer)
        }]
    )
    verdict = msg.content[0].text.strip().upper()
    return verdict == "TRUE"

⚠️ 주의: 수치 점수(예: "2.7점")를 직접 요청하면 LLM은 계산 없이 그럴싸한 값을 생성합니다. 항상 분류/판정 방식을 사용하세요.

💡 실무 노하우: 항목별 True/False 판정 후 합산하는 설문조사 방식이 단일 통합 점수 요청보다 정확합니다. 토너먼트(pairwise) 방식도 유효합니다.

확인: 출력이 정확히 "TRUE" 또는 "FALSE"인지 검증합니다. 불명확한 출력은 재시도 로직을 추가하세요.


Step 4 — Perplexity 라이브러리 (HuggingFace evaluate)

목표: HuggingFace evaluate 라이브러리로 PPL을 일괄 계산합니다.

import evaluate

perplexity = evaluate.load("perplexity", module_type="metric")

texts = ["The quick brown fox jumps over the lazy dog.",
         "LLM evaluation requires careful metric selection."]

results = perplexity.compute(
    predictions=texts,
    model_id="gpt2"   # 로컬 모델 ID
)
print(f"Mean PPL: {results['mean_perplexity']:.4f}")

📚 참고: HuggingFace evaluate 공식 문서 https://huggingface.co/docs/evaluate/index — perplexity metric 사용법 상세 안내.

확인: mean_perplexity 키가 결과에 포함되면 정상 동작입니다.


Step 5 — 동작 확인 (테스트)

목표: Step 1~4 통합 테스트를 실행합니다.

# Step 2 통합 검증
test_lps = [-0.105, -0.223, -0.105, -0.105]  # A B C D 예시
result = compute_ppl(test_lps)
assert 1.14 < result["ppl"] < 1.15, f"예상 PPL ≈ 1.1443, 실제: {result['ppl']}"
print(f"PPL 검증 통과: {result['ppl']:.4f}")

# Step 3 통합 검증
verdict = judge_answer(
    question="파이썬의 GIL이란?",
    answer="Global Interpreter Lock으로 스레드 동시 실행을 제한합니다."
)
print(f"Judge 판정: {'TRUE' if verdict else 'FALSE'}")

확인: assert가 통과하고 Judge 판정이 TRUE이면 파이프라인이 정상 동작합니다.


5. 적용 사례 (공신력 오픈소스 5가지+)

  • OpenAI Python SDK (https://github.com/openai/openai-python) — logprobs=True 파라미터로 토큰별 로그 확률 추출, PPL 계산의 데이터 소스.

  • Anthropic Python SDK (https://github.com/anthropics/anthropic-sdk-python) — client.messages.create로 Claude 기반 LLM-as-judge 분류 판정 구현.

  • HuggingFace evaluate (https://github.com/huggingface/evaluate) — evaluate.load("perplexity")로 로컬 모델 PPL 일괄 계산, 표준 메트릭 레지스트리 제공.

  • lm-evaluation-harness (https://github.com/EleutherAI/lm-evaluation-harness) — EleutherAI가 관리하는 표준 LLM 평가 프레임워크. PPL 포함 수십 개 벤치마크 지원.

  • HuggingFace lighteval (https://github.com/huggingface/lighteval) — HuggingFace 공식 경량 평가 프레임워크. 다중 태스크 PPL/정확도 평가에 특화.

  • OpenAI evals (https://github.com/openai/evals) — OpenAI 공식 평가 프레임워크. LLM-as-judge 패턴(match, classify 등) 표준 구현 포함.

  • LangChain evaluation (https://github.com/langchain-ai/langchain) — LangSmith와 연계한 LLM 평가 체인. QAEvalChain 등 판정 기반 평가 컴포넌트 제공.

  • DeepEval (https://github.com/confident-ai/deepeval) — pytest 스타일 LLM 평가 프레임워크. Faithfulness, Answer Relevancy 등 항목별 판정 메트릭 내장.

📚 참고: OpenAI Cookbook — Using Logprobs https://cookbook.openai.com/examples/using_logprobs — logprobs 활용 실전 예제 모음.


6. 핵심 원리

수식: PPL = exp(H_e), H_e = (1/N) × Σᵢ [-ln(P(tᵢ))]

  • 각 토큰 tᵢ의 모델 확률 P(tᵢ)에 -ln()을 취해 양수 손실값으로 변환합니다.
  • N개 토큰의 평균을 구하면 정규화된 교차 엔트로피 H_e(nats)를 얻습니다.
  • exp(H_e)로 자연로그 단위를 제거하면 PPL이 됩니다.
  • nats 기준이든 bits 기준이든 PPL 값은 동일합니다(단위 불변성).

LLM-as-judge 원리: LLM은 확률적 토큰 생성기이므로, "2.7"이라는 출력도 실제 계산이 아닌 컨텍스트에서 가장 그럴싸한 다음 토큰을 순차 생성한 결과입니다. True/False 같은 이진 분류는 모델의 핵심 강점(분포 판별)을 활용하는 방식입니다.


7. 변형·확장

  • Token-level vs Document-level PPL: 단일 토큰 손실 평균(token-level)과 문서 전체를 하나의 시퀀스로 처리하는 방식(document-level)은 경계 처리 방식이 다릅니다.

  • Per-domain PPL: 도메인별 평가 데이터셋을 구성해 PPL을 측정하면 모델의 도메인 적합성을 검증할 수 있습니다. 평가용 LLM과 피평가 LLM의 도메인 PPL이 유사해야 공정한 LLM-as-judge가 가능합니다.

  • LLM-as-judge with rubric: 항목별 판정 기준(rubric)을 프롬프트에 명시하면 일관성이 높아집니다. G-Eval, MT-Bench 논문에서 검증된 패턴입니다.

  • Pairwise comparison: 두 모델의 출력을 나란히 제시하고 어느 쪽이 더 좋은지 판정하게 하는 토너먼트 방식입니다. 절대 점수보다 상대 비교가 더 안정적입니다.

  • Chain-of-thought judge: 판정 전에 근거를 먼저 생성하도록 유도하면 일관성이 향상됩니다.


8. 다른 도구·접근과의 비교 (3-way)

지표 특징 한계
PPL (퍼플렉시티) NLP 보정 없음, 로짓 직접 계산, 단위 불변 토크나이저 의존, 의미·사실성 미반영
BLEU/ROUGE/METEOR 정답 참조 텍스트와 비교, 전통 NLP 방식 정답 데이터 필요, 의미적 유사성 미반영
LLM-as-judge (G-Eval, MT-Bench) 높은 확장성, 참조 없이 평가 가능 환각·자기 편향·positional bias risk

💡 실무 노하우: 세 접근을 병용하세요. PPL로 도메인 적합성 사전 검증 → LLM-as-judge로 의미 품질 평가 → 인간 평가로 최종 교정.


9. 한계·트레이드오프

1. PPL 자체 한계 - 토크나이저가 다르면 PPL 값도 달라지므로 다른 토크나이저 기반 모델 간 직접 비교는 불가합니다. - 도메인 외삽(out-of-domain) 텍스트에 대해서는 해석이 어렵습니다. - 문법·유창성은 측정하지만 사실 정확성이나 응용 적합성은 반영하지 않습니다.

2. LLM-as-judge 편향 3종 - 자기 편향(Self-preference): 평가 LLM이 자신과 유사한 스타일의 출력에 높은 점수를 줍니다. - Positional bias: 두 답변 비교 시 첫 번째 답변에 점수를 높이 주는 경향입니다. 순서를 바꿔 2회 평가하고 평균을 내면 완화됩니다. - 길이 편향: 더 긴 답변에 점수를 높이 주는 경향이 있습니다.

3. 벤치마크 데이터 누수(Leakage) - 학습 데이터에 평가 데이터가 포함되면 PPL이 인위적으로 낮아집니다. - 이미 학습된 데이터는 PPL이 낮게 나오는 점을 항상 고려해야 합니다.

⚠️ 주의: LLM 평가 점수가 높다고 사용자 만족도가 반드시 높은 것은 아닙니다. 벤치마크 튜닝(Benchmark Tuning)으로 인해 점수는 높으나 실용성이 낮은 모델이 존재합니다.


10. 최신 권장 패턴 (2026-05 기준, Anthropic·OpenAI 공식 문서)

Structured Output for Evaluation: Anthropic Tool Use 또는 OpenAI JSON mode로 평가 결과를 구조화된 스키마로 강제하면 파싱 오류와 말짓기를 줄일 수 있습니다.

# Anthropic Tool Use 기반 구조화 판정 예시
tools = [{
    "name": "submit_judgment",
    "description": "답변 평가 결과를 제출합니다.",
    "input_schema": {
        "type": "object",
        "properties": {
            "is_accurate": {"type": "boolean"},
            "is_relevant": {"type": "boolean"},
            "reasoning": {"type": "string"}
        },
        "required": ["is_accurate", "is_relevant", "reasoning"]
    }
}]

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=256,
    tools=tools,
    messages=[{"role": "user", "content": judge_prompt}]
)

Multi-judge Ensembles: 단일 모델 편향을 줄이기 위해 여러 모델의 판정을 다수결로 결합합니다. Anthropic Cookbook의 evaluator 패턴이 이 방식을 권장합니다.

Rubric-based Scoring: "1~5점"이 아닌 각 점수에 구체 기준을 명시한 rubric을 프롬프트에 포함합니다. DeepEval·G-Eval 표준 패턴입니다.

Pairwise + Absolute 병행: Chatbot Arena(LMSYS)와 MT-Bench는 절대 점수와 쌍별 비교를 병행해 평가 신뢰성을 높입니다.

📚 참고: Anthropic 공식 문서 https://docs.anthropic.com/en/docs — Tool use 및 평가 패턴 상세 가이드.


11. 메타인지 자기평가

본인 LLM 평가 시스템에 적용 가능한지 검증 절차입니다.

Step 1 — 현재 상태 점검

grep -r "perplexity\|logprobs\|judge\|evaluator" your_eval_code/

Step 2 — 적용 가능성 평가

  • 조건 1: logprobs API에 접근 가능한 모델을 사용하는가? (OpenAI gpt-*, vLLM 서빙 모델, llama-cpp-python)
  • 조건 2: LLM-as-judge의 판정이 True/False 또는 명확한 분류 기반인가? (직접 수치 점수 요청은 제거)
  • 조건 3: 평가용 LLM과 피평가 LLM의 도메인 PPL이 비교 가능한 수준인가?
  • 조건 4: 평가 결과의 일관성 확보를 위한 고정 기준(온도=0, 동일 입력 고정)이 있는가?

Step 3 — 점진 적용 1. 기존 메트릭에 PPL 병기해 도메인 적합성 사전 검증 추가. 2. LLM-as-judge 도입 시 True/False 분류로 전환하고 multi-judge(≥2개 모델) 적용. 3. 평가 점수와 실제 사용자 피드백 간 상관관계(코헨의 κ 등)를 주기적으로 측정.

난이도
에피소드
질문
카드를 로딩 중...
답변

클릭하거나 Space를 눌러 뒤집기

0 / 0
학습 진도 0%
이동   Space 뒤집기   R 셔플